package com.zkjl.protect_business.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Sets;

import com.zkjl.protect_business.common.Consts;
import com.zkjl.protect_business.common.Status;
import com.zkjl.protect_business.common.UserThreadLocal;
import com.zkjl.protect_business.entity.User;
import com.zkjl.protect_business.exception.SecurityException;
import com.zkjl.protect_business.repository.UserRepository;
import com.zkjl.protect_business.service.CustomUserDetailsService;
import com.zkjl.protect_business.utils.JwtUtil;
import com.zkjl.protect_business.utils.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpMethod;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import static com.zkjl.protect_business.common.Status.UNAUTHORIZED;

/**
 * <p>
 * Jwt 认证过滤器
 * </p>
 */
@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private CustomConfig customConfig;

    @Autowired
    UserRepository userRepository;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private JwtConfig jwtConfig;


    /**
     * 处理跨域
     * @param request
     * @param response
     */
    public static void toDealWithCROS(HttpServletRequest request, HttpServletResponse response) {
        response.setHeader("x-frame-options", "SAMEORIGIN");
        // 不使用*，自动适配跨域域名，避免携带Cookie时失效
        String origin = request.getHeader("Origin");
        if (org.apache.commons.lang3.StringUtils.isNotBlank(origin)) {

            response.setHeader("Access-Control-Allow-Origin", origin); }
        // 自适应所有自定义头
        String headers = request.getHeader("Access-Control-Request-Headers");
        if(org.apache.commons.lang3.StringUtils.isNotBlank(headers)) {

            response.setHeader("Access-Control-Allow-Headers", headers);
            response.setHeader("Access-Control-Expose-Headers", headers);
        }
        // 允许跨域的请求方法类型
        response.setHeader("Access-Control-Allow-Methods", "*");
        // 预检命令（OPTIONS）缓存时间，单位：秒
        response.setHeader("Access-Control-Max-Age", "3600");
        // 明确许可客户端发送Cookie，不允许删除字段即可
        response.setHeader("Access-Control-Allow-Credentials", "true");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {


        if (checkIgnores(request)) {
            toDealWithCROS(request, response);
            filterChain.doFilter(request, response);
            return;
        }

        String jwt = jwtUtil.getJwtFromRequest(request);

        if (StrUtil.isNotBlank(jwt)) {
            try {
                String username = jwtUtil.getUsernameFromJWT(jwt);

                UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext()
                        .setAuthentication(authentication);


                User user = userRepository.findByUserName(username);
                //登录成功
                UserThreadLocal.set(user);//将user对象放置在本地线程中，方便controller和service获取

                // 刷新token过期时间
                Long ttl = jwtConfig.getTtl();
                stringRedisTemplate.opsForValue()
                        .set(Consts.REDIS_JWT_KEY_PREFIX + username, jwt, ttl, TimeUnit.MINUTES);

                filterChain.doFilter(request, response);
            } catch (SecurityException e) {
                String origin = request.getHeader("Origin");
                ResponseUtil.renderJson(response, origin, UNAUTHORIZED.getCode(), e.getMessage());
            } catch (UsernameNotFoundException e) {
                String origin = request.getHeader("Origin");
                ResponseUtil.renderJson(response, origin, UNAUTHORIZED.getCode(), e.getMessage());
            } catch (Exception e) {
                log.error("异常：", e);
            }
        } else {
            String origin = request.getHeader("Origin");
            ResponseUtil.renderJson(response, origin, UNAUTHORIZED, null);
        }
    }

    /**
     * 请求是否不需要进行权限拦截
     *
     * @param request 当前请求
     * @return true - 忽略，false - 不忽略
     */
    private boolean checkIgnores(HttpServletRequest request) {
        String method = request.getMethod();

        if (method.equals("OPTIONS")) {
            return true;
        }

        HttpMethod httpMethod = HttpMethod.resolve(method);
        if (ObjectUtil.isNull(httpMethod)) {
            httpMethod = HttpMethod.GET;
        }

        Set<String> ignores = Sets.newHashSet();

        switch (httpMethod) {
            case GET:
                ignores.addAll(customConfig.getIgnores()
                        .getGet());
                break;
            case PUT:
                ignores.addAll(customConfig.getIgnores()
                        .getPut());
                break;
            case HEAD:
                ignores.addAll(customConfig.getIgnores()
                        .getHead());
                break;
            case POST:
                ignores.addAll(customConfig.getIgnores()
                        .getPost());
                break;
            case PATCH:
                ignores.addAll(customConfig.getIgnores()
                        .getPatch());
                break;
            case TRACE:
                ignores.addAll(customConfig.getIgnores()
                        .getTrace());
                break;
            case DELETE:
                ignores.addAll(customConfig.getIgnores()
                        .getDelete());
                break;
            case OPTIONS:
                ignores.addAll(customConfig.getIgnores()
                        .getOptions());
                break;
            default:
                break;
        }

        ignores.addAll(customConfig.getIgnores()
                .getPattern());

        if (CollUtil.isNotEmpty(ignores)) {
            for (String ignore : ignores) {
                AntPathRequestMatcher matcher = new AntPathRequestMatcher(ignore, method);
                if (matcher.matches(request)) {
                    return true;
                }
            }
        }

        return false;
    }

}
